home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / ChoiceFormat.java < prev    next >
Text File  |  1998-09-22  |  17KB  |  446 lines

  1. /*
  2.  * @(#)ChoiceFormat.java    1.11 97/02/06
  3.  *
  4.  * (C) Copyright Taligent, Inc. 1996-1997 - All Rights Reserved
  5.  * (C) Copyright IBM Corp. 1996-1997 - All Rights Reserved
  6.  *
  7.  * Portions copyright (c) 1996-1997 Sun Microsystems, Inc. All Rights Reserved.
  8.  *
  9.  *   The original version of this source code and documentation is copyrighted
  10.  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  11.  * materials are provided under terms of a License Agreement between Taligent
  12.  * and Sun. This technology is protected by multiple US and International
  13.  * patents. This notice and attribution to Taligent may not be removed.
  14.  *   Taligent is a registered trademark of Taligent, Inc.
  15.  *
  16.  * Permission to use, copy, modify, and distribute this software
  17.  * and its documentation for NON-COMMERCIAL purposes and without
  18.  * fee is hereby granted provided that this copyright notice
  19.  * appears in all copies. Please refer to the file "copyright.html"
  20.  * for further important copyright and licensing information.
  21.  *
  22.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  23.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  24.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  25.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  26.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  27.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  28.  *
  29.  */
  30.  
  31. package java.text;
  32. import java.text.Utility;
  33.  
  34. /**
  35.  * A <code>ChoiceFormat</code> allows you to attach a format to a range of numbers.
  36.  * It is generally used in a <code>MessageFormat</code> for handling plurals.
  37.  * The choice is specified with an ascending list of doubles, where each item
  38.  * specifies a half-open interval up to the next item:
  39.  * <blockquote>
  40.  * <pre>
  41.  * X matches j if and only if limit[j] <= X < limit[j+1]
  42.  * </pre>
  43.  * </blockquote>
  44.  * If there is no match, then either the first or last index is used, depending
  45.  * on whether the number (X) is too low or too high.
  46.  *
  47.  * <p>
  48.  * <strong>Note:</strong>
  49.  * <code>ChoiceFormat</code> differs from the other <code>Format</code>
  50.  * classes in that you create a <code>ChoiceFormat</code> object with a
  51.  * constructor (not with a <code>getInstance</code> style factory
  52.  * method). The factory methods aren't necessary because <code>ChoiceFormat</code>
  53.  * doesn't require any complex setup for a given locale. In fact,
  54.  * <code>ChoiceFormat</code> doesn't implement any locale specific behavior.
  55.  *
  56.  * <p>
  57.  * When creating a <code>ChoiceFormat</code>, you must specify an array of formats
  58.  * and an array of limits. The length of these arrays must be the same.
  59.  * For example,
  60.  * <ul>
  61.  * <li>
  62.  *     <em>limits</em> = {1,2,3,4,5,6,7}<br>
  63.  *     <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}
  64.  * <lie>
  65.  *     <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br>
  66.  *     <em>formats</em> = {"no files", "one file", "many files"}<br>
  67.  *     (<code>nextDouble</code> can be used to get the next higher double, to
  68.  *     make the half-open interval.)
  69.  * </ul>
  70.  *
  71.  * <p>
  72.  * Here is a simple example that shows formatting and parsing:
  73.  * <blockquote>
  74.  * <pre>
  75.  * double[] limits = {1,2,3,4,5,6,7};
  76.  * String[] monthNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"};
  77.  * ChoiceFormat form = new ChoiceFormat(limits, monthNames);
  78.  * ParsePosition status = new ParsePosition(0);
  79.  * for (double i = 0.0; i <= 8.0; ++i) {
  80.  *     status.setIndex(0);
  81.  *     System.out.println(i + " -> " + form.format(i) + " -> "
  82.  *                              + form.parse(form.format(i),status));
  83.  * }
  84.  * </pre>
  85.  * </blockquote>
  86.  * Here is a more complex example, with a pattern format:
  87.  * <blockquote>
  88.  * <pre>
  89.  * double[] filelimits = {0,1,2};
  90.  * String[] filepart = {"are no files","is one file","are {2} files"};
  91.  * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
  92.  * Format[] testFormats = {fileform, null, NumberFormat.getInstance()};
  93.  * MessageFormat pattform = new MessageFormat("There {0} on {1}");
  94.  * pattform.setFormats(testFormats);
  95.  * Object[] testArgs = {null, "ADisk", null};
  96.  * for (int i = 0; i < 4; ++i) {
  97.  *     testArgs[0] = new Integer(i);
  98.  *     testArgs[2] = testArgs[0];
  99.  *     System.out.println(pattform.format(testArgs));
  100.  * }
  101.  * </pre>
  102.  * </blockquote>
  103.  * @see          DecimalFormat
  104.  * @see          MessageFormat
  105.  * @version      1.11 02/06/97
  106.  * @author       Mark Davis
  107.  */
  108. public class ChoiceFormat extends NumberFormat {
  109.     /**
  110.      * Sets the pattern.
  111.      * @param newPattern See the class description.
  112.      */
  113.     public void applyPattern(String newPattern) {
  114.             StringBuffer[] segments = new StringBuffer[2];
  115.             for (int i = 0; i < segments.length; ++i) {
  116.                 segments[i] = new StringBuffer();       // later, use single
  117.             }
  118.             double[] newChoiceLimits = new double[30];    // current limit
  119.             String[] newChoiceFormats = new String[30];   // later, use Vectors
  120.             int count = 0;
  121.             int part = 0;
  122.             double startValue = 0;
  123.             double oldStartValue = Double.NaN;
  124.             boolean inQuote = false;
  125.             for (int i = 0; i < newPattern.length(); ++i) {
  126.                 char ch = newPattern.charAt(i);
  127.                 if (ch == '<' || ch == '#' || ch == '\u2264') {
  128.                     if (segments[0].equals("")) {
  129.                         throw new IllegalArgumentException();
  130.                     }
  131.                     try {
  132.                         startValue = Double.valueOf(segments[0].toString()).doubleValue();
  133.                     } catch (Exception e) {
  134.                         throw new IllegalArgumentException();
  135.                     }
  136.                     if (ch == '<') {
  137.                         startValue = nextDouble(startValue);
  138.                     }
  139.                     if (startValue <= oldStartValue) {
  140.                         throw new IllegalArgumentException();
  141.                     }
  142.                     segments[0].setLength(0);
  143.                     part = 1;
  144.                 } else if (ch == '|') {
  145.                 //System.out.println("***" + startValue + "," + segments[1].toString());
  146.                     newChoiceLimits[count] = startValue;
  147.                     newChoiceFormats[count] = segments[1].toString();
  148.                     ++count;
  149.                     oldStartValue = startValue;
  150.                     segments[1].setLength(0);
  151.                     part = 0;
  152.                 } else {
  153.                     segments[part].append(ch);
  154.                 }
  155.             }
  156.             // clean up last one
  157.             if (part == 1) {
  158.                 newChoiceLimits[count] = startValue;
  159.              newChoiceFormats[count] = segments[1].toString();
  160.                 //System.out.println("***" + newChoiceLimits[count] + "," + newChoiceFormats[count]);
  161.                 ++count;
  162.             }
  163.             // compact arrays
  164.             //System.out.println("***" + count);
  165.             choiceLimits = new double[count];
  166.             System.arraycopy(newChoiceLimits, 0, choiceLimits, 0, count);
  167.             choiceFormats = new String[count];
  168.             System.arraycopy(newChoiceFormats, 0, choiceFormats, 0, count);
  169.             //for (int i = 0; i < choiceLimits.length; ++i) {
  170.             //System.out.println("&&<" + choiceLimits[i]);
  171.             //System.out.println("&&>" + choiceFormats[i]);
  172.             //}
  173.     }
  174.     /**
  175.      * Gets the pattern.
  176.      */
  177.  
  178.     public String toPattern() {
  179.         StringBuffer result = new StringBuffer();
  180.         //System.out.println("&&&" + choiceLimits.length);
  181.         for (int i = 0; i < choiceLimits.length; ++i) {
  182.             //System.out.println("&&<" + choiceLimits[i] + ";"
  183.             //    + Long.toString(Double.doubleToLongBits(choiceLimits[i]),16)
  184.             //    + ";" + choiceFormats[i]);
  185.             if (i != 0) {
  186.                 result.append('|');
  187.             }
  188.             // choose based upon which has less precision
  189.             // approximate that by choosing the closest one to an integer.
  190.             // could do better, but it's not worth it.
  191.             double less = previousDouble(choiceLimits[i]);
  192.             double tryLessOrEqual = Math.abs(Math.IEEEremainder(choiceLimits[i], 1.0d));
  193.             double tryLess = Math.abs(Math.IEEEremainder(less, 1.0d));
  194.             if (tryLessOrEqual < tryLess) {
  195.                 result.append(""+choiceLimits[i]);
  196.                 result.append('#');
  197.             } else {
  198.                 result.append(""+less);
  199.                 result.append('<');
  200.             }
  201.             result.append(choiceFormats[i].toString());
  202.         }
  203.         return result.toString();
  204.     }
  205.  
  206.     /**
  207.      * Constructs with limits and corresponding formats based on the pattern.
  208.      */
  209.     public ChoiceFormat(String newPattern)  {
  210.         applyPattern(newPattern);
  211.     }
  212.     /**
  213.      * Constructs with the limits and the corresponding formats.
  214.      * @see #setChoices
  215.      */
  216.     public ChoiceFormat(double[] limits, String[] formats) {
  217.         setChoices(limits, formats);
  218.     }
  219.     /**
  220.      * Set the choices to be used in formatting.
  221.      * @param limits contains the top value that you want
  222.      * parsed with that format,and should be in ascending sorted order. When
  223.      * formatting X, the choice will be the i, where limit[i] <= X < limit[i+1].
  224.      * @param formats are the formats you want to use for each limit.
  225.      * They can be either Format objects or Strings.
  226.      * When formatting with object Y,
  227.      * if the object is a NumberFormat, then ((NumberFormat) Y).format(X)
  228.      * is called. Otherwise Y.toString() is called.
  229.      */
  230.     public void setChoices(double[] limits, String formats[]) {
  231.         choiceLimits = limits;
  232.         choiceFormats = formats;
  233.     }
  234.     /**
  235.      * Get the limits passed in the constructor.
  236.      * @return the limits.
  237.      */
  238.     public double[] getLimits() {
  239.         return choiceLimits;
  240.     }
  241.     /**
  242.      * Get the formats passed in the constructor.
  243.      * @return the formats.
  244.      */
  245.     public Object[] getFormats() {
  246.         return choiceFormats;
  247.     }
  248.  
  249.     // Overrides
  250.  
  251.     /**
  252.      * Specialization of format. This method really calls
  253.      * <code>format(double, StringBuffer, FieldPosition)</code>
  254.      * thus the range of longs that are supported is only equal to
  255.      * the range that can be stored by double. This will never be
  256.      * a practical limitation.
  257.      */
  258.     public StringBuffer format(long number, StringBuffer toAppendTo,
  259.                                FieldPosition status) {
  260.         return format((double)number, toAppendTo, status);
  261.     }
  262.  
  263.     public StringBuffer format(double number, StringBuffer toAppendTo,
  264.                                FieldPosition status) {
  265.         // find the number
  266.         int i;
  267.         for (i = 0; i < choiceLimits.length; ++i) {
  268.             if (!(number >= choiceLimits[i])) {
  269.                 // same as number < choiceLimits, except catchs NaN
  270.                 break;
  271.             }
  272.         }
  273.         --i;
  274.         if (i < 0) i = 0;
  275.         // return either a formatted number, or a string
  276.         return toAppendTo.append(choiceFormats[i]);
  277.     }
  278.  
  279.     public Number parse(String text, ParsePosition status) {
  280.         // find the best number (defined as the one with the longest parse)
  281.         int start = status.index;
  282.         int furthest = start;
  283.         double bestNumber = Double.NaN;
  284.         double tempNumber = 0.0;
  285.         for (int i = 0; i < choiceFormats.length; ++i) {
  286.             String tempString = choiceFormats[i];
  287.             if (text.regionMatches(start, tempString, 0, tempString.length())) {
  288.                 status.index = tempString.length();
  289.                 tempNumber = choiceLimits[i];
  290.                 if (status.index > furthest) {
  291.                     furthest = status.index;
  292.                     bestNumber = tempNumber;
  293.                     if (furthest == text.length()) break;
  294.                 }
  295.             }
  296.         }
  297.         status.index = furthest;
  298.         return new Double(bestNumber);
  299.     }
  300.  
  301.     /**
  302.      * Finds the least double greater than d.
  303.      * If NaN, returns same value.
  304.      * <p>Used to make half-open intervals.
  305.      * @see #previousDouble
  306.      */
  307.     public static final double nextDouble (double d) {
  308.         return nextDouble(d,true);
  309.     }
  310.  
  311.     /**
  312.      * Finds the greatest double less than d.
  313.      * If NaN, returns same value.
  314.      * @see #nextDouble
  315.      */
  316.     public static final double previousDouble (double d) {
  317.         return nextDouble(d,false);
  318.     }
  319.  
  320.     /**
  321.      * Overrides Cloneable
  322.      */
  323.     public Object clone()
  324.     {
  325.         ChoiceFormat other = (ChoiceFormat) super.clone();
  326.         // for primitives or immutables, shallow clone is enough
  327.         other.choiceLimits = (double[]) choiceLimits.clone();
  328.         other.choiceFormats = (String[]) choiceFormats.clone();
  329.         return other;
  330.     }
  331.  
  332.     /**
  333.      * Generates a hash code for the message format object.
  334.      */
  335.     public int hashCode() {
  336.         int result = choiceLimits.length;
  337.         if (choiceFormats.length > 0) {
  338.             // enough for reasonable distribution
  339.             result ^= choiceFormats[choiceFormats.length-1].hashCode();
  340.         }
  341.         return result;
  342.     }
  343.  
  344.     /**
  345.      * Equality comparision between two
  346.      */
  347.     public boolean equals(Object obj) {
  348.         if (this == obj)                      // quick check
  349.             return true;
  350.         if (getClass() != obj.getClass())
  351.             return false;
  352.         ChoiceFormat other = (ChoiceFormat) obj;
  353.         return (Utility.arrayEquals(choiceLimits,other.choiceLimits)
  354.             && Utility.arrayEquals(choiceFormats,other.choiceFormats));
  355.     }
  356.     // ===============privates===========================
  357.     private double[] choiceLimits;
  358.     private String[] choiceFormats;
  359.  
  360.     /*
  361.     static final long SIGN          = 0x8000000000000000L;
  362.     static final long EXPONENT      = 0x7FF0000000000000L;
  363.     static final long SIGNIFICAND   = 0x000FFFFFFFFFFFFFL;
  364.  
  365.     private static double nextDouble (double d, boolean positive) {
  366.         if (Double.isNaN(d) || Double.isInfinite(d)) {
  367.                 return d;
  368.             }
  369.         long bits = Double.doubleToLongBits(d);
  370.         long significand = bits & SIGNIFICAND;
  371.         if (bits < 0) {
  372.             significand |= (SIGN | EXPONENT);
  373.         }
  374.         long exponent = bits & EXPONENT;
  375.         if (positive) {
  376.             significand += 1;
  377.             // FIXME fix overflow & underflow
  378.         } else {
  379.             significand -= 1;
  380.             // FIXME fix overflow & underflow
  381.         }
  382.         bits = exponent | (significand & ~EXPONENT);
  383.         return Double.longBitsToDouble(bits);
  384.     }
  385.     */
  386.  
  387.     /*
  388.      * Finds the least double greater than d (if positive == true),
  389.      * or the greatest double less than d (if positive == false).
  390.      * If NaN, returns same value.
  391.      *
  392.      * Does not affect floating-point flags,
  393.      *  provided these member functions do not:
  394.      *          Double.longBitsToDouble ()
  395.      *          Double.doubleToLongBits ()
  396.      *          Double.IsNaN ()
  397.      */
  398.  
  399.     static final long SIGN                = 0x8000000000000000L;
  400.     static final long EXPONENT            = 0x7FF0000000000000L;
  401.     static final long POSITIVEINFINITY    = 0x7FF0000000000000L;
  402.  
  403.     public static double nextDouble (double d, boolean positive) {
  404.  
  405.         /* filter out NaN's */
  406.         if (Double.isNaN(d)) {
  407.             return d;
  408.         }
  409.  
  410.         /* zero's are also a special case */
  411.         if (d == 0.0) {
  412.             double smallestPositiveDouble = Double.longBitsToDouble(1L);
  413.             if (positive) {
  414.                 return smallestPositiveDouble;
  415.             } else {
  416.                 return -smallestPositiveDouble;
  417.             }
  418.         }
  419.  
  420.         /* if entering here, d is a nonzero value */
  421.  
  422.         /* hold all bits in a long for later use */
  423.         long bits = Double.doubleToLongBits(d);
  424.  
  425.         /* strip off the sign bit */
  426.         long magnitude = bits & ~SIGN;
  427.  
  428.         /* if next double away from zero, increase magnitude */
  429.         if ((bits > 0) == positive) {
  430.             if (magnitude != POSITIVEINFINITY) {
  431.                 magnitude += 1;
  432.             }
  433.         }
  434.         /* else decrease magnitude */
  435.         else {
  436.             magnitude -= 1;
  437.         }
  438.  
  439.         /* restore sign bit and return */
  440.         long signbit = bits & SIGN;
  441.         return Double.longBitsToDouble (magnitude | signbit);
  442.     }
  443.  
  444. }
  445.  
  446.